//-------------------------------------------------------------------
//	Copyright (c) 2010-2011  Zhirnov Andrey
//	This file is part of the "UXGEN_ResourceConverter" project.
//	See copyright notice in "Converter.h".
//-------------------------------------------------------------------

#include "MeshConverters.h"
#include "..\\..\\Core\\CConverter.h"
#include "lib3ds\\lib3ds.h"
#include "..\\Texture\\CTextureConverter.h"

#pragma comment (lib, "..\\..\\Libs\\3ds\\lib3ds.lib")

struct T3DSParser
{
private:
	Lib3dsFile	*	_p3DS;
	TModelInfo &	_sModel;
	string			_sMtrPath;
	string			_sTexPath;
	string			_sFilePath;

private:
	bool _AddSubMesh(TMultiMesh &sMultiMesh, Lib3dsMesh *pMesh, uint32_array_t &aSubMeshMtr);
	bool _AddMaterial(TMeshMaterialsList &sMtrList, uint uMtrIdx, const uint32_array_t &aSubMeshMtr);
	bool _ConvertToMeshes();
	bool _ConvertToMesh();
	bool _ConvertTextures(TMaterial &sMtr, const string &sFilePath, const string &sTexPath);
	bool _ConvertTexture(string &sFileName, const string &sFilePath, const string &sTexPath);

	bool _CheckMtrPath();

public:
	T3DSParser(TModelInfo &model);
	~T3DSParser();

	bool Load(const char *pFileName);
	bool Converting();
	bool GetInfo(string &sInfo);
};




bool C3DSConverter::ConvertMesh(TModelInfo &sInfo)
{
	T3DSParser	s_3ds( sInfo );

	return s_3ds.Load( sInfo.pFileName ) && s_3ds.Converting();
}


bool C3DSConverter::ConvertFrameAnim(TModelInfo &sInfo)
{
	return false;
}


bool C3DSConverter::ConvertSceletal(TModelInfo &sInfo)
{
	return false;
}


bool C3DSConverter::ConvertAutoType(TModelInfo &sInfo)
{
	return ConvertMesh( sInfo );
}






T3DSParser::T3DSParser(TModelInfo &model): _sModel(model), _p3DS(nullptr)
{
}


T3DSParser::~T3DSParser()
{
	if ( _p3DS != nullptr )
	{
		lib3ds_file_free( _p3DS );
		_p3DS = nullptr;
	}
}


bool T3DSParser::Load(const char *pFileName)
{
	_p3DS = lib3ds_file_open( pFileName );

	String::GetFilePath( pFileName, _sFilePath );
	_sFilePath << "\\";
	_sTexPath = _sFilePath;

	return ( _p3DS != nullptr );
}


bool CalcBoundingBox(const float *pVertices, uint uCount, uint uStride, aabb_f &sBBox)
{
	vec3	s_min( pVertices ),
			s_max( s_min );

	if ( uStride == 0 )
		uStride = sizeof(float)*3;

	for (usize i = 1; i < uCount; ++i)
	{
		float const * vert = cptr_move<const float *>( pVertices, uStride * i );

		if ( vert[0] < s_min.x )	s_min.x = vert[0];
		if ( vert[1] < s_min.y )	s_min.y = vert[1];
		if ( vert[2] < s_min.z )	s_min.z = vert[2];

		if ( vert[0] > s_max.x )	s_max.x = vert[0];
		if ( vert[1] > s_max.y )	s_max.y = vert[1];
		if ( vert[2] > s_max.z )	s_max.z = vert[2];
	}
	
	sBBox.Set( s_min, s_max );
	return true;
}


bool T3DSParser::_CheckMtrPath()
{
	if ( CORE->DirectoryExist( _sMtrPath.cstr() ) )
		return true;

	return CORE->NewDirectory( _sMtrPath.cstr() );
}


bool T3DSParser::_AddSubMesh(TMultiMesh &sMultiMesh, Lib3dsMesh *pMesh, uint32_array_t &aSubMeshMtr)
{
	float_array_t	a_vertices,
					a_normals,
					a_texcoords;
	int32_array_t	a_materials;

	uint			u_processed = 0;
	int const		i_max_mtrs	= _p3DS->nmaterials;
	
	a_vertices.Reserve(	 pMesh->nfaces * 9 );
	a_texcoords.Reserve( pMesh->nfaces * 6 );
	a_normals.Resize(	 pMesh->nfaces * 9, false );

	
	lib3ds_mesh_calculate_vertex_normals( pMesh, (float(*)[3])a_normals.ptr() );

	for (int face = 0; face < pMesh->nfaces; ++face)
	{
		Lib3dsFace	*p_face		= &pMesh->faces[face];
		bool		b_new_mtr	= true;
		int			i_mtr_idx	= p_face->material;

		if ( i_mtr_idx > i_max_mtrs )
			i_mtr_idx = i_max_mtrs;

		for (uint i = 0; i < a_materials.Count(); ++i)
		{
			if ( a_materials[i] == p_face->material ) {
				b_new_mtr = false;
				break;
			}
		}

		if ( b_new_mtr )
			a_materials.PushBack( p_face->material );


		if ( pMesh->texcos != nullptr )
		{
			a_texcoords.Append( pMesh->texcos[ p_face->index[0] ], 2 );
			a_texcoords.Append( pMesh->texcos[ p_face->index[1] ], 2 );
			a_texcoords.Append( pMesh->texcos[ p_face->index[2] ], 2 );
		}
		a_vertices.Append( pMesh->vertices[ p_face->index[0] ], 3 );
		a_vertices.Append( pMesh->vertices[ p_face->index[1] ], 3 );
		a_vertices.Append( pMesh->vertices[ p_face->index[2] ], 3 );
	}


	for (uint i = 0; i < a_materials.Count(); ++i)
	{
		float_array_t	a_data;
		uint32_array_t	a_idx;
		uint			u_idx = 0;
		bool const		b_texcoords = !a_texcoords.Empty();
		
		for (int face = 0; face < pMesh->nfaces; ++face)
		{
			Lib3dsFace	*p_face = &pMesh->faces[face];

			if ( p_face->material != a_materials[i] )
				continue;

			for (uint j = 0; j < 3; ++j)
			{
				a_data.Append( &a_vertices[ face*9 +j*3 ], 3 );
				a_data.Append( &a_normals[ face*9 +j*3 ], 3 );

				if ( b_texcoords )
					a_data.Append( &a_texcoords[ face*6 +j*2 ], 2 );
			}

			a_idx.PushBack( u_idx++ );
			a_idx.PushBack( u_idx++ );
			a_idx.PushBack( u_idx++ );
		}


		TMultiMesh::TSubMesh	s_submesh;
		const uint				u_vert_offset	= (uint)sMultiMesh.aVertices.Size();
		uint					u_vert_size		= sizeof(float) * (b_texcoords ? 8 : 6);

		s_submesh.uIdxCount		= (uint)a_idx.Count();
		s_submesh.uIdxOffset	= (uint)sMultiMesh.aIndices.Size();
		s_submesh.eMode			= gl_primitive::TRIANGLE;
		s_submesh.eType			= gl_index::UINT;
		s_submesh.sName			= string(pMesh->name) << "_" << i;
		
		s_submesh.aAttribs.PushBack( TVertexAttrib( e_vertex_attrib::POSITION, 3, u_vert_size, u_vert_offset, gl_vertex::FLOAT, false, 0 ) );
		s_submesh.aAttribs.PushBack( TVertexAttrib( e_vertex_attrib::NORMAL,   3, u_vert_size, u_vert_offset+sizeof(float)*3, gl_vertex::FLOAT, false, 0 ) );
		
		if ( b_texcoords )
			s_submesh.aAttribs.PushBack( TVertexAttrib( e_vertex_attrib::TEXCOORD_0, 2, u_vert_size, u_vert_offset+sizeof(float)*6, gl_vertex::FLOAT, false, 0 ) );

		if ( !CalcBoundingBox( a_data.ptr(), u_idx, u_vert_size, s_submesh.sBBox ) )
			s_submesh.sBBox = aabb_f();

		sMultiMesh.aSMIndices.PushBack( sMultiMesh.aSubMeshes.Count() );

		sMultiMesh.aIndices.Append(  a_idx.Begin(),  a_idx.End() );
		sMultiMesh.aVertices.Append( a_data.Begin(), a_data.End() );
		sMultiMesh.aSubMeshes.PushBack( s_submesh );
		aSubMeshMtr.PushBack( a_materials[i] );
	}

	return true;
}


bool T3DSParser::_AddMaterial(TMeshMaterialsList &sMtrList, uint uMtrIdx, const uint32_array_t &aSubMeshMtr)
{
	Lib3dsMaterial *	p_mtr	= _p3DS->materials[uMtrIdx];
	TMaterial			s_mtr;

	s_mtr.sName					= p_mtr->name;
	s_mtr.sDiffuse.sMapFileName = p_mtr->texture1_map.name;
	s_mtr.sDiffuse.sColor		= color4f( p_mtr->diffuse[0], p_mtr->diffuse[1], p_mtr->diffuse[2], 1.f );
	s_mtr.sSpecular.sMapFileName= p_mtr->specular_map.name;
	s_mtr.sSpecular.sColor		= color4f( p_mtr->specular[0], p_mtr->specular[1], p_mtr->specular[2], 1.f );
	s_mtr.sEmission.sMapFileName= p_mtr->self_illum_map.name;
	s_mtr.sEmission.sColor		= color4f( p_mtr->self_illum, p_mtr->self_illum, p_mtr->self_illum, 1.f );
	s_mtr.sAmbient.sColor		= color4f( p_mtr->ambient[0], p_mtr->ambient[1], p_mtr->ambient[2], 1.f );
	s_mtr.sNormalMap			= p_mtr->bump_map.name;
	s_mtr.fShininess			= p_mtr->shininess;
	s_mtr.sOpacityMap			= p_mtr->opacity_map.name;
	s_mtr.sReflectionMap		= p_mtr->reflection_map.name;
	s_mtr.bRSEnabled			= true;
	s_mtr.sStates.eCullMode		= e_cull_mode::FACE_CCW | (p_mtr->two_sided ? 0 : e_cull_mode::BACK);


	sMtrList.aMaterials.PushBack( TMeshMaterialsList::TSubMeshMaterial() );
	TMeshMaterialsList::TSubMeshMaterial &	s_submesh_mtr = sMtrList.aMaterials.Back();

	s_submesh_mtr.sFileName << _sMtrPath;
	
	// convert textures //
	if ( EnumCmp( _sModel.eModelFlags, MDF_CONVERT_TEXTURES ) )
	{
		_ConvertTextures( s_mtr, _sFilePath, s_submesh_mtr.sFileName );
	}

	s_submesh_mtr.sFileName << s_mtr.sName << ".ocf";


	for (uint i = 0; i < aSubMeshMtr.Count(); ++i)
	{
		if ( uMtrIdx == aSubMeshMtr[i] )
			s_submesh_mtr.aMeshIndices.PushBack( i );
	}

	return SaveMaterial( _sModel.sMtrPath + s_submesh_mtr.sFileName, s_mtr );
}


bool T3DSParser::_ConvertTextures(TMaterial &sMtr, const string &sFilePath, const string &sTexPath)
{
	if ( !CORE->DirectoryExist( sFilePath.cstr() ) )
		if ( !CORE->NewDirectory( sFilePath.cstr() ) )
			return false;

	bool	ret = true;

	ret &= _ConvertTexture( sMtr.sAmbient.sMapFileName, sFilePath, sTexPath );
	ret &= _ConvertTexture( sMtr.sDiffuse.sMapFileName, sFilePath, sTexPath );
	ret &= _ConvertTexture( sMtr.sSpecular.sMapFileName, sFilePath, sTexPath );
	ret &= _ConvertTexture( sMtr.sEmission.sMapFileName, sFilePath, sTexPath );
	ret &= _ConvertTexture( sMtr.sNormalMap, sFilePath, sTexPath );
	ret &= _ConvertTexture( sMtr.sOpacityMap, sFilePath, sTexPath );
	ret &= _ConvertTexture( sMtr.sReflectionMap, sFilePath, sTexPath );

	return ret;
}


bool T3DSParser::_ConvertTexture(string &sFileName, const string &sFilePath, const string &sTexPath)
{
	if ( sFileName.Empty() )
		return true;

	string & s_file_name = sFileName;
	bool	 ret = true;

	ret &= SYSTEMS->pTexConv->Create( TT_TEXTURE_2D ) == S_OK;
	ret &= SYSTEMS->pTexConv->Load( (_sTexPath + s_file_name).cstr() ) == S_OK;
	
	String::CutFileExt( s_file_name );
	s_file_name << ".tex";

	ret &= SYSTEMS->pTexConv->Save( (sFilePath + s_file_name).cstr() ) == S_OK;
	ret &= SYSTEMS->pTexConv->Delete() == S_OK;

	s_file_name >> sTexPath;

	return ret;
}


bool T3DSParser::_ConvertToMeshes()
{
	const char	*p_name = String::GetFileName( _sModel.pFileName );

	for (int i = 0; i < _p3DS->nmeshes; ++i)
	{
		_sModel.sData.aMeshes.PushBack( TModelData::TModelMesh( p_name ) );
		_sModel.sData.aMaterials.PushBack( TModelData::TMeshMtrInfo( p_name ) );

		TModelData::TModelMesh&	s_model		= _sModel.sData.aMeshes.Back();
		TMeshMaterial		  &	s_material	= _sModel.sData.aMaterials.Back().sMaterial;
		uint32_array_t			a_submesh_mtr;

		s_material.Create( TMeshMaterial::MTR_LIST );
		s_model.sMesh.Create( TMesh::MULTI_MESH );
		s_model.uMaterialIdx	= (uint)_sModel.sData.aMaterials.Count()-1;

		TMultiMesh			&	s_multimesh	= *s_model.sMesh.sData.pMesh;
		TMeshMaterialsList	&	s_mtr_list	= *s_material.sData.pMtrList;

		_AddSubMesh( s_multimesh, _p3DS->meshes[i], a_submesh_mtr );
	
		s_multimesh.aLODs.PushBack( 0 );
		s_multimesh.aLODs.PushBack( s_multimesh.aSMIndices.Count() );

		// convert materials //
		if ( EnumCmp( _sModel.eModelFlags, MDF_CONVERT_MATERIALS ) )
		{
			_sMtrPath = _p3DS->meshes[i]->name;
			_sMtrPath << '\\';
		
			if ( _CheckMtrPath() )
			{
				for (int i = 0; i < _p3DS->nmaterials; ++i)
				{
					_AddMaterial( s_mtr_list, i, a_submesh_mtr );
				}
			}
		}
	}
	
	return true;
}


bool T3DSParser::_ConvertToMesh()
{
	const char	*p_name = String::GetFileName( _sModel.pFileName );

	_sModel.sData.aMeshes.PushBack( TModelData::TModelMesh( p_name ) );
	_sModel.sData.aMaterials.PushBack( TModelData::TMeshMtrInfo( p_name ) );

	TModelData::TModelMesh&	s_model		= _sModel.sData.aMeshes.Back();
	TMeshMaterial		  &	s_material	= _sModel.sData.aMaterials.Back().sMaterial;
	uint32_array_t			a_submesh_mtr;

	s_material.Create( TMeshMaterial::MTR_LIST );
	s_model.sMesh.Create( TMesh::MULTI_MESH );
	s_model.uMaterialIdx	= (uint)_sModel.sData.aMaterials.Count()-1;

	TMultiMesh			&	s_multimesh	= *s_model.sMesh.sData.pMesh;
	TMeshMaterialsList	&	s_mtr_list	= *s_material.sData.pMtrList;


	// convert meshes //
	for (int i = 0; i < _p3DS->nmeshes; ++i)
	{
		_AddSubMesh( s_multimesh, _p3DS->meshes[i], a_submesh_mtr );
	}
	
	s_multimesh.aLODs.PushBack( 0 );
	s_multimesh.aLODs.PushBack( s_multimesh.aSMIndices.Count() );

	
	// convert materials //
	if ( EnumCmp( _sModel.eModelFlags, MDF_CONVERT_MATERIALS ) )
	{
		_sMtrPath = p_name;
		String::CutFileExt( _sMtrPath );
		_sMtrPath << '\\';
		
		if ( _CheckMtrPath() )
		{
			for (int i = 0; i < _p3DS->nmaterials; ++i)
			{
				_AddMaterial( s_mtr_list, i, a_submesh_mtr );
			}
		}
	}

	return true;
}


bool T3DSParser::Converting()
{
	if ( _p3DS == nullptr )
		return false;

	if ( EnumCmp( _sModel.eModelFlags, MDF_MERGE_MESHES ) )
		return _ConvertToMesh();
	else
		return _ConvertToMeshes();
}

